#include "Cubeta.h"
using namespace Hash;

/*
 * Inicializa una cubeta con  0 registros y tamanio de dispersion 0
 * Metadata cubeta: TD y Bytes Libres
 */
Cubeta::Cubeta(int tamanioCubeta) {
	this->setRegistros(new vector<RegistroCubeta*> );
	this->setTamanioCubeta(tamanioCubeta);
}

/**
 * Inicializa una cubeta con un tamanio de dispersionn y una tamanio de cubeta
 **/
Cubeta::Cubeta(int tamanioDispersion, int tamanioCubeta) {

	this->setRegistros(new vector<RegistroCubeta*> );
	this->setTamanioDispersion(tamanioDispersion);
	this->setTamanioCubeta(tamanioCubeta);
	// Bytes libres = tamanio de la cubeta - metadata -( TD + BytesLibres + CantidadRegistros)
	this->setBytesLibres(this->getTamanioCubeta() - (sizeof(int) * 3));
}

/**
 * Devuelve el tamanio que ocupa la cubeta en el archivo
 **/
unsigned int Cubeta::getTamanioDelBuffer() {
	// TD + BytesLibres + Cant.Registros
	int tamanioDelBuffer = 3 * sizeof(int);

	for (unsigned int i = 0; i < this->getRegistros()->size(); i++) {
		// Tamanio Clave Registro + Cantidad de offsets
		tamanioDelBuffer += 2 * sizeof(int);
		//Longitud de la clave del registro
		tamanioDelBuffer += this->getRegistros()->at(i)->getClave().size();
		unsigned int cantOffsets = this->getRegistros()->at(i)->getOffsets()->size();
		//Cantidad de offsets por el tamanio de cada uno
		tamanioDelBuffer += cantOffsets * sizeof(int);
	}

	return tamanioDelBuffer;

}

/*
 *
 */
void Cubeta::marcarComoBorrado() {

}

/*
 *  Devuelve el numero de registro si lo encuentra
 *  Devuelve -1 si no lo encuentra
 */
int Cubeta::buscar(string clave) {
	unsigned int i = 0;
	while (i < this->getRegistros()->size()) {
		if (this->getRegistros()->at(i)->getClave() == clave) {
			return i;
		}
		i++;
	}
	return -1;
}

/*
 * Si hay lugar guarda el registro en la cubeta, y devuelve OK
 * Si no hay lugar devuelve LLENA
 * Si el registro ya existia DUPLICADO
 */
EstadoCubeta Cubeta::insertar(RegistroCubeta &nuevoRegistro) {

	int numeroRegistro = this->buscar(nuevoRegistro.getClave());
	// Si no existia en la cubeta, se agrega
	if (numeroRegistro == -1) {
		int bytesNuevoRegistro = nuevoRegistro.getBytesOcupados();
		if ((this->getTamanioDelBuffer() + bytesNuevoRegistro) > this->getCargaMaxima()) {
			return LLENO;
		} else {
			//hago una copia del registro (con el contructor de copia) para insertarlo
			RegistroCubeta* registroAInsertar = new RegistroCubeta(nuevoRegistro);
			this->getRegistros()->push_back(registroAInsertar);
			this->setBytesLibres(this->getBytesLibres() - (registroAInsertar)->getBytesOcupados());
			return OK;
		}
	} else {

		// Un registro con esa clave ya existia
		// Si el offset no estaba lo agrega, y verifica si quedo desbordada
		// Si el offset estaba no hace nada

		bool offsetInsertado = this->registros->at(numeroRegistro)->insertar(nuevoRegistro.getOffsets()->at(0));

		//Si hubo un cambio en el registro verifico que no quede desbordada la cubeta
		if (offsetInsertado) {

			//si no queda espacio libre, se lleno la cubeta, no se puede insertar el offset
			if ((this->getBytesLibres()) < 0) {
				//devuelvo el registro entero en el que se agrego el offset

				//Elimino del vector de offsets de la cubeta en el que quise agregar, queda desbordada la cubeta
				for(unsigned int i = 0;i < this->getRegistros()->at(numeroRegistro)->getOffsets()->size();i++){
					nuevoRegistro.insertar(this->getRegistros()->at(numeroRegistro)->getOffsets()->at(i));
				}
				RegistroCubeta* registroAborrar = this->getRegistros()->at(numeroRegistro);
				this->getRegistros()->erase(this->getRegistros()->begin() + numeroRegistro);
				delete registroAborrar;
				return LLENO;
			} else {
				//Se actualizan los bytes libres porque se pudo insertar el offset
				this->setBytesLibres(this->getBytesLibres() - sizeof(int));
				//se borra el registro nuevo que no se inserto completo
				return OK;
			}
		}else{
			return SINACCION;
		}
	}

}

/*
 *  Elimina un registro de la cubeta o si tiene mas de un offset, solo elimina el offset
 */
EstadoCubeta Cubeta::eliminar(RegistroCubeta* registro) {

	//Se busca el registro que se quiere eliminar
	int posicion = this->buscar(registro->getClave());

	if (posicion != -1) {

		//OJO aca estoy asumiendo que el registro tiene solo un OFFSET ! por eso tomo la posicion 0
		int posicionOffset = this->getRegistros()->at(posicion)->buscar(registro->getOffsets()->at(0));

		if (posicionOffset != -1) {

			this->getRegistros()->at(posicion)->eliminar(registro->getOffsets()->at(0));

			//Si sacando ese offset el registro queda vacio, lo borro de la cubeta
			if (this->getRegistros()->at(posicion)->getOffsets()->size() == 0) {

				this->setBytesLibres(this->getBytesLibres() + registro->getBytesOcupados());
				//Lo borro del vector y libero la memora que ocupaba ese registro
				RegistroCubeta* registroAborrar = this->getRegistros()->at(posicion);
				this->getRegistros()->erase((this->getRegistros()->begin()) + (posicion));
				delete registroAborrar;
			} else {
				//Si sacando ese offset el registro no queda vacio, solo actualizo los bytes libres de la cubeta
				this->setBytesLibres(this->getBytesLibres() + sizeof(int));
			}
			//Verifico en que estado quedo la cubeta, si no tiene registros
			// quedo VACIA
			if (this->getRegistros()->size() > 0) {
				return OK;
			} else {
				return VACIO;
			}
		} else {
			//queria eliminar un offset de una clave que no estaba guardado
			return SINACCION;
		}
	} else {
		//queria eliminar un registro con una clave que no existia
		return SINACCION;
	}
}

/*
 * Devuelve en un buffer de memoria la cubeta para poder guardarse
 */
char* Cubeta::construirBuffer() {

	//agrupo los atributos
	stringstream* ss = new stringstream(stringstream::in | stringstream::out | stringstream::binary);
	ss->seekp(0, ios::end);

	//Tamanio Dispersion
	unsigned int tamanioDispersion = this->getTamanioDispersion();
	ss->write((char*) &(tamanioDispersion), sizeof(int));

	//Bytes Libres
	unsigned int bytesLibres = this->getBytesLibres();
	ss->write((char*) &(bytesLibres), sizeof(int));

	//Cantidad de Registros
	unsigned int cantidadDeRegistros = this->getRegistros()->size();
	ss->write((char*) &cantidadDeRegistros, sizeof(int));

	//Registro por Registro de la Cubeta
	for (unsigned int i = 0; i < cantidadDeRegistros; i++) {

		//Tamanio y Clave del Registro
		string claveRegistro = this->getRegistros()->at(i)->getClave();
		unsigned int tamanioClave = claveRegistro.size();
		ss->write((char*) &tamanioClave, sizeof(int));
		ss->write(claveRegistro.c_str(), tamanioClave);

		//Offsets del Registro
		unsigned int cantidadOffsets = this->getRegistros()->at(i)->getOffsets()->size();
		ss->write((char*) &cantidadOffsets, sizeof(int));
		for (unsigned int j = 0; j < cantidadOffsets; j++) {
			unsigned int offset = this->getRegistros()->at(i)->getOffsets()->at(j);
			ss->write((char*) &offset, sizeof(int));
		}

	}

	//construyo el buffer
	unsigned int tamanioDelBuffer = this->getTamanioDelBuffer();
	char* buffer = new char[tamanioDelBuffer];
	ss->read(buffer, tamanioDelBuffer);
	delete ss;

	//lo devuelvo
	return buffer;
}

/*
 * Recibe el bloque del archivo donde esta guardada la cubeta
 * Hidrata la cubeta con todos sus atributos a partir del bloque del archivo
 */
void Cubeta::hidratarse(char * buffer, unsigned int tamanioDelBuffer) {

	//escribo el buffer en el stringStream
	stringstream* ss = new stringstream(stringstream::in | stringstream::out | stringstream::binary);
	ss->seekg(0, ios::beg);
	ss->write(buffer, tamanioDelBuffer);

	//int Tamanio Dispersion
	int td;
	ss->read((char*) &(td), sizeof(int));
	this->setTamanioDispersion(td);

	//int Bytes Libres
	int bytesLibres;
	ss->read((char*) &(bytesLibres), sizeof(int));
	this->setBytesLibres(bytesLibres);

	//int Cantidad Registros
	unsigned int cantidadRegistros;
	ss->read((char*) &(cantidadRegistros), sizeof(int));

	unsigned int cantidadOffsets;
	unsigned int offset;
	for (unsigned int i = 0; i < cantidadRegistros; i++) {

		RegistroCubeta* registro = new RegistroCubeta();
		registro->setClave(Utils::ObtenerSiguienteStringVariable(ss));
		ss->read((char*) &(cantidadOffsets), sizeof(int));

		for (unsigned int j = 0; j < cantidadOffsets; j++) {
			ss->read((char*) &(offset), sizeof(int));
			registro->getOffsets()->push_back(offset);
		}
		this->getRegistros()->push_back(registro);
	}

	delete ss;

}

int Cubeta::getBytesOcupados() {

	return (this->getTamanioCubeta() - this->getBytesLibres());
}

int Cubeta::getBytesLibres() {

	return this->getTamanioCubeta() - this->getTamanioDelBuffer();
}

void Cubeta::setBytesLibres(int libres) {
	this->bytesLibres = libres;
}

#include "../Archivos/Configuracion.h"

/* Devuelve el maximo de carga en la cubeta */
int Cubeta::getCargaMaxima() const {

	Configuracion* config = Configuracion::getInstancia();
	unsigned int capacidadMaximaCubeta = config->getCapacidadMaximaCubeta();

	return ((this->getTamanioCubeta() * capacidadMaximaCubeta) / 100);
}

int Cubeta::getTamanioCubeta() const {
	return tamanioCubeta;
}

void Cubeta::setTamanioCubeta(int tamanioCubeta) {
	this->tamanioCubeta = tamanioCubeta;
}

int Cubeta::getTamanioDispersion() {
	return this->tamanioDispersion;
}

void Cubeta::setTamanioDispersion(int tamanioDispersion) {
	this->tamanioDispersion = tamanioDispersion;
}

vector<RegistroCubeta*>* Cubeta::getRegistros() {
	return this->registros;
}

void Cubeta::setRegistros(vector<RegistroCubeta*>* registros) {
	this->registros = registros;
}

/*
 *  Libera la memoria utilizada por la cubeta
 */
Cubeta::~Cubeta() {
	for (unsigned int i = 0; i < this->registros->size(); i++) {
		delete this->registros->at(i);
	}
	delete this->registros; // elimino la lista de registros
}
